#!/bin/bash

# 应用更新和回滚脚本
# Application Update and Rollback Script

set -e

# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color

# 配置变量
SCRIPT_VERSION="1.0.0"
PROJECT_NAME="personal-cloud-notes"
PROJECT_DIR="/var/www/$PROJECT_NAME"
BACKUP_DIR="$PROJECT_DIR/data/backups"
UPDATE_LOG="/var/log/${PROJECT_NAME}-update.log"
PM2_USER="pm2"

# Git配置
GIT_REPO="https://github.com/your-username/personal-cloud-notes.git"
GIT_BRANCH="main"

# 更新配置
BACKUP_BEFORE_UPDATE=true
RUN_TESTS_AFTER_UPDATE=true
ROLLBACK_ON_FAILURE=true
MAX_ROLLBACK_VERSIONS=5

# 函数：打印带颜色的消息
print_message() {
    local color=$1
    local message=$2
    echo -e "${color}${message}${NC}"
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $message" >> "$UPDATE_LOG"
}

# 函数：打印标题
print_title() {
    local title=$1
    echo
    print_message $CYAN "=================================="
    print_message $CYAN "$title"
    print_message $CYAN "=================================="
}

# 函数：打印进度
print_progress() {
    local current=$1
    local total=$2
    local message=$3
    local percent=$((current * 100 / total))
    print_message $BLUE "[$current/$total] ($percent%) $message"
}

# 函数：检查是否以root权限运行
check_root() {
    if [ "$EUID" -ne 0 ]; then
        print_message $RED "此脚本需要root权限运行"
        print_message $BLUE "请使用: sudo $0"
        exit 1
    fi
}

# 函数：检查Git仓库状态
check_git_status() {
    print_message $YELLOW "检查Git仓库状态..."
    
    cd "$PROJECT_DIR"
    
    # 检查是否是Git仓库
    if [ ! -d ".git" ]; then
        print_message $YELLOW "不是Git仓库，尝试初始化..."
        git init
        git remote add origin "$GIT_REPO"
        git fetch origin
        git checkout -b "$GIT_BRANCH" "origin/$GIT_BRANCH"
    fi
    
    # 检查远程仓库连接
    if ! git remote -v | grep -q origin; then
        git remote add origin "$GIT_REPO"
    fi
    
    # 获取当前版本信息
    local current_commit=$(git rev-parse HEAD 2>/dev/null || echo "unknown")
    local current_branch=$(git branch --show-current 2>/dev/null || echo "unknown")
    
    print_message $BLUE "当前分支: $current_branch"
    print_message $BLUE "当前提交: ${current_commit:0:8}"
    
    # 检查是否有未提交的更改
    if ! git diff --quiet 2>/dev/null || ! git diff --cached --quiet 2>/dev/null; then
        print_message $YELLOW "⚠️  检测到未提交的更改"
        git status --porcelain
        
        read -p "是否继续更新? (y/N): " -n 1 -r
        echo
        if [[ ! $REPLY =~ ^[Yy]$ ]]; then
            print_message $BLUE "更新已取消"
            exit 0
        fi
    fi
    
    print_message $GREEN "Git仓库状态检查完成"
}

# 函数：检查更新可用性
check_updates_available() {
    print_message $YELLOW "检查更新可用性..."
    
    cd "$PROJECT_DIR"
    
    # 获取远程更新
    git fetch origin "$GIT_BRANCH"
    
    # 比较本地和远程版本
    local local_commit=$(git rev-parse HEAD)
    local remote_commit=$(git rev-parse "origin/$GIT_BRANCH")
    
    if [ "$local_commit" = "$remote_commit" ]; then
        print_message $GREEN "已是最新版本"
        return 1
    else
        local commits_behind=$(git rev-list --count HEAD..origin/$GIT_BRANCH)
        print_message $BLUE "发现 $commits_behind 个新提交"
        
        # 显示更新日志
        print_message $BLUE "更新内容预览:"
        git log --oneline HEAD..origin/$GIT_BRANCH | head -10 | while read line; do
            print_message $YELLOW "  $line"
        done
        
        return 0
    fi
}

# 函数：创建更新前备份
create_update_backup() {
    print_message $YELLOW "创建更新前备份..."
    
    local backup_timestamp=$(date +%Y%m%d-%H%M%S)
    local backup_name="pre-update-$backup_timestamp"
    local backup_path="$BACKUP_DIR/$backup_name"
    
    mkdir -p "$backup_path"
    
    # 备份应用文件
    print_message $BLUE "备份应用文件..."
    tar -czf "$backup_path/app-files.tar.gz" \
        --exclude="node_modules" \
        --exclude="data/logs" \
        --exclude="data/temp" \
        --exclude="data/users" \
        -C "$PROJECT_DIR" .
    
    # 备份数据库
    print_message $BLUE "备份数据库..."
    if [ -f "$PROJECT_DIR/.env" ]; then
        local db_password=$(grep "DB_PASSWORD=" "$PROJECT_DIR/.env" | cut -d'=' -f2)
        local db_user=$(grep "DB_USER=" "$PROJECT_DIR/.env" | cut -d'=' -f2)
        local db_name=$(grep "DB_NAME=" "$PROJECT_DIR/.env" | cut -d'=' -f2)
        
        mysqldump -u"$db_user" -p"$db_password" "$db_name" | gzip > "$backup_path/database.sql.gz"
    fi
    
    # 备份配置文件
    print_message $BLUE "备份配置文件..."
    cp "$PROJECT_DIR/.env" "$backup_path/" 2>/dev/null || true
    cp -r "$PROJECT_DIR/config" "$backup_path/" 2>/dev/null || true
    
    # 记录版本信息
    cd "$PROJECT_DIR"
    git rev-parse HEAD > "$backup_path/version.txt"
    git log -1 --pretty=format:"%H %s" >> "$backup_path/version.txt"
    
    # 保存备份信息
    echo "$backup_name" > "$BACKUP_DIR/.last_backup"
    
    print_message $GREEN "备份完成: $backup_path"
    echo "$backup_path"
}

# 函数：停止应用服务
stop_application() {
    print_message $YELLOW "停止应用服务..."
    
    # 停止PM2应用
    if sudo -u $PM2_USER pm2 list 2>/dev/null | grep -q "$PROJECT_NAME"; then
        sudo -u $PM2_USER pm2 stop "$PROJECT_NAME" || true
        print_message $BLUE "PM2应用已停止"
    fi
    
    # 等待进程完全停止
    sleep 3
    
    print_message $GREEN "应用服务停止完成"
}

# 函数：启动应用服务
start_application() {
    print_message $YELLOW "启动应用服务..."
    
    cd "$PROJECT_DIR"
    
    # 启动PM2应用
    sudo -u $PM2_USER pm2 start ecosystem.config.js --env production
    
    # 等待应用启动
    sleep 5
    
    # 检查应用状态
    if sudo -u $PM2_USER pm2 list | grep -q "$PROJECT_NAME.*online"; then
        print_message $GREEN "应用服务启动成功"
        return 0
    else
        print_message $RED "应用服务启动失败"
        return 1
    fi
}

# 函数：更新应用代码
update_application_code() {
    print_message $YELLOW "更新应用代码..."
    
    cd "$PROJECT_DIR"
    
    # 拉取最新代码
    git pull origin "$GIT_BRANCH"
    
    # 记录新版本信息
    local new_commit=$(git rev-parse HEAD)
    local new_version=$(git log -1 --pretty=format:"%H %s")
    
    print_message $BLUE "更新到版本: ${new_commit:0:8}"
    print_message $BLUE "提交信息: $new_version"
    
    print_message $GREEN "代码更新完成"
}

# 函数：更新依赖包
update_dependencies() {
    print_message $YELLOW "更新依赖包..."
    
    cd "$PROJECT_DIR"
    
    # 检查package.json是否有变化
    if git diff HEAD~1 --name-only | grep -q "package.json"; then
        print_message $BLUE "检测到package.json变化，更新依赖..."
        
        # 清理现有依赖
        rm -rf node_modules package-lock.json
        
        # 安装新依赖
        npm cache clean --force
        npm install --production --no-optional
        
        print_message $GREEN "依赖包更新完成"
    else
        print_message $BLUE "package.json无变化，跳过依赖更新"
    fi
}

# 函数：运行数据库迁移
run_database_migrations() {
    print_message $YELLOW "运行数据库迁移..."
    
    cd "$PROJECT_DIR"
    
    # 检查是否有数据库迁移脚本
    if [ -f "scripts/migrate.js" ]; then
        node scripts/migrate.js
        print_message $GREEN "数据库迁移完成"
    elif [ -d "database/migrations" ]; then
        # 运行迁移文件
        for migration in database/migrations/*.sql; do
            if [ -f "$migration" ]; then
                print_message $BLUE "运行迁移: $(basename "$migration")"
                
                if [ -f "$PROJECT_DIR/.env" ]; then
                    local db_password=$(grep "DB_PASSWORD=" "$PROJECT_DIR/.env" | cut -d'=' -f2)
                    local db_user=$(grep "DB_USER=" "$PROJECT_DIR/.env" | cut -d'=' -f2)
                    local db_name=$(grep "DB_NAME=" "$PROJECT_DIR/.env" | cut -d'=' -f2)
                    
                    mysql -u"$db_user" -p"$db_password" "$db_name" < "$migration"
                fi
            fi
        done
        print_message $GREEN "数据库迁移完成"
    else
        print_message $BLUE "未找到数据库迁移，跳过"
    fi
}

# 函数：运行更新后测试
run_post_update_tests() {
    print_message $YELLOW "运行更新后测试..."
    
    cd "$PROJECT_DIR"
    
    # 运行部署验证测试
    if [ -f "scripts/deployment-test.js" ]; then
        if node scripts/deployment-test.js; then
            print_message $GREEN "更新后测试通过"
            return 0
        else
            print_message $RED "更新后测试失败"
            return 1
        fi
    else
        print_message $YELLOW "未找到测试脚本，跳过测试"
        return 0
    fi
}

# 函数：回滚到指定版本
rollback_to_backup() {
    local backup_name=$1
    
    print_message $YELLOW "回滚到备份: $backup_name"
    
    local backup_path="$BACKUP_DIR/$backup_name"
    
    if [ ! -d "$backup_path" ]; then
        print_message $RED "备份不存在: $backup_path"
        return 1
    fi
    
    # 停止应用
    stop_application
    
    # 恢复应用文件
    print_message $BLUE "恢复应用文件..."
    cd "$PROJECT_DIR"
    tar -xzf "$backup_path/app-files.tar.gz" -C .
    
    # 恢复数据库
    if [ -f "$backup_path/database.sql.gz" ]; then
        print_message $BLUE "恢复数据库..."
        
        if [ -f "$PROJECT_DIR/.env" ]; then
            local db_password=$(grep "DB_PASSWORD=" "$PROJECT_DIR/.env" | cut -d'=' -f2)
            local db_user=$(grep "DB_USER=" "$PROJECT_DIR/.env" | cut -d'=' -f2)
            local db_name=$(grep "DB_NAME=" "$PROJECT_DIR/.env" | cut -d'=' -f2)
            
            gunzip -c "$backup_path/database.sql.gz" | mysql -u"$db_user" -p"$db_password" "$db_name"
        fi
    fi
    
    # 恢复配置文件
    print_message $BLUE "恢复配置文件..."
    cp "$backup_path/.env" "$PROJECT_DIR/" 2>/dev/null || true
    cp -r "$backup_path/config" "$PROJECT_DIR/" 2>/dev/null || true
    
    # 重新安装依赖
    print_message $BLUE "重新安装依赖..."
    rm -rf node_modules package-lock.json
    npm install --production --no-optional
    
    # 启动应用
    start_application
    
    print_message $GREEN "回滚完成"
}

# 函数：列出可用的备份
list_available_backups() {
    print_message $BLUE "可用的备份版本:"
    
    if [ -d "$BACKUP_DIR" ]; then
        local backups=($(ls -1t "$BACKUP_DIR" | grep "pre-update-" | head -$MAX_ROLLBACK_VERSIONS))
        
        if [ ${#backups[@]} -eq 0 ]; then
            print_message $YELLOW "未找到可用备份"
            return 1
        fi
        
        for i in "${!backups[@]}"; do
            local backup="${backups[$i]}"
            local backup_path="$BACKUP_DIR/$backup"
            
            if [ -f "$backup_path/version.txt" ]; then
                local version_info=$(cat "$backup_path/version.txt")
                print_message $YELLOW "  $((i+1)). $backup"
                print_message $BLUE "     版本: ${version_info:0:8}"
            else
                print_message $YELLOW "  $((i+1)). $backup"
            fi
        done
        
        return 0
    else
        print_message $YELLOW "备份目录不存在"
        return 1
    fi
}

# 函数：清理旧备份
cleanup_old_backups() {
    print_message $YELLOW "清理旧备份..."
    
    if [ -d "$BACKUP_DIR" ]; then
        local backups=($(ls -1t "$BACKUP_DIR" | grep "pre-update-"))
        local backup_count=${#backups[@]}
        
        if [ $backup_count -gt $MAX_ROLLBACK_VERSIONS ]; then
            local to_remove=$((backup_count - MAX_ROLLBACK_VERSIONS))
            print_message $BLUE "删除 $to_remove 个旧备份..."
            
            for ((i=MAX_ROLLBACK_VERSIONS; i<backup_count; i++)); do
                local backup="${backups[$i]}"
                rm -rf "$BACKUP_DIR/$backup"
                print_message $BLUE "删除备份: $backup"
            done
        fi
    fi
    
    print_message $GREEN "备份清理完成"
}

# 函数：发送更新通知
send_update_notification() {
    local status=$1
    local message=$2
    
    print_message $BLUE "更新通知: $status - $message"
    
    # 这里可以集成邮件通知、Slack通知等
    # 示例：发送邮件通知
    # if command -v mail >/dev/null 2>&1; then
    #     echo "$message" | mail -s "个人云笔记更新通知 - $status" admin@example.com
    # fi
}

# 函数：显示帮助信息
show_help() {
    echo "个人云笔记应用更新脚本 v$SCRIPT_VERSION"
    echo
    echo "用法: $0 [选项]"
    echo
    echo "选项:"
    echo "  --help, -h           显示帮助信息"
    echo "  --version, -v        显示版本信息"
    echo "  --check              检查更新可用性"
    echo "  --update             执行应用更新"
    echo "  --rollback [备份名]  回滚到指定备份"
    echo "  --list-backups       列出可用备份"
    echo "  --force              强制更新，跳过确认"
    echo "  --no-backup          更新时不创建备份"
    echo "  --no-tests           更新后不运行测试"
    echo "  --branch BRANCH      指定Git分支"
    echo
    echo "示例:"
    echo "  $0 --check                    # 检查更新"
    echo "  $0 --update                   # 执行更新"
    echo "  $0 --rollback pre-update-xxx  # 回滚到指定版本"
    echo "  $0 --list-backups             # 列出可用备份"
    echo
}

# 主函数
main() {
    # 处理命令行参数
    local action="update"
    local force_update=false
    local rollback_target=""
    
    while [[ $# -gt 0 ]]; do
        case $1 in
            --help|-h)
                show_help
                exit 0
                ;;
            --version|-v)
                echo "个人云笔记更新脚本 v$SCRIPT_VERSION"
                exit 0
                ;;
            --check)
                action="check"
                shift
                ;;
            --update)
                action="update"
                shift
                ;;
            --rollback)
                action="rollback"
                rollback_target="$2"
                shift 2
                ;;
            --list-backups)
                action="list-backups"
                shift
                ;;
            --force)
                force_update=true
                shift
                ;;
            --no-backup)
                BACKUP_BEFORE_UPDATE=false
                shift
                ;;
            --no-tests)
                RUN_TESTS_AFTER_UPDATE=false
                shift
                ;;
            --branch)
                GIT_BRANCH="$2"
                shift 2
                ;;
            *)
                print_message $RED "未知选项: $1"
                show_help
                exit 1
                ;;
        esac
    done
    
    # 开始执行
    print_title "个人云笔记应用更新管理 v$SCRIPT_VERSION"
    
    # 检查权限
    check_root
    
    # 创建更新日志
    touch "$UPDATE_LOG"
    
    case $action in
        check)
            check_git_status
            if check_updates_available; then
                print_message $GREEN "有可用更新"
                exit 0
            else
                print_message $BLUE "已是最新版本"
                exit 1
            fi
            ;;
        
        list-backups)
            list_available_backups
            ;;
        
        rollback)
            if [ -z "$rollback_target" ]; then
                print_message $YELLOW "请选择要回滚的备份版本:"
                if list_available_backups; then
                    read -p "请输入备份编号或名称: " rollback_input
                    
                    if [[ "$rollback_input" =~ ^[0-9]+$ ]]; then
                        # 数字输入，获取对应的备份名称
                        local backups=($(ls -1t "$BACKUP_DIR" | grep "pre-update-" | head -$MAX_ROLLBACK_VERSIONS))
                        local index=$((rollback_input - 1))
                        if [ $index -ge 0 ] && [ $index -lt ${#backups[@]} ]; then
                            rollback_target="${backups[$index]}"
                        else
                            print_message $RED "无效的备份编号"
                            exit 1
                        fi
                    else
                        rollback_target="$rollback_input"
                    fi
                else
                    exit 1
                fi
            fi
            
            print_message $YELLOW "准备回滚到: $rollback_target"
            if [ "$force_update" = false ]; then
                read -p "确认回滚? (y/N): " -n 1 -r
                echo
                if [[ ! $REPLY =~ ^[Yy]$ ]]; then
                    print_message $BLUE "回滚已取消"
                    exit 0
                fi
            fi
            
            if rollback_to_backup "$rollback_target"; then
                print_message $GREEN "回滚成功"
                send_update_notification "ROLLBACK_SUCCESS" "应用已回滚到版本: $rollback_target"
            else
                print_message $RED "回滚失败"
                send_update_notification "ROLLBACK_FAILED" "应用回滚失败: $rollback_target"
                exit 1
            fi
            ;;
        
        update)
            local update_start_time=$(date)
            local update_errors=0
            local backup_path=""
            
            # 1. 检查Git状态
            print_progress 1 8 "检查Git仓库状态"
            check_git_status || ((update_errors++))
            
            # 2. 检查更新可用性
            print_progress 2 8 "检查更新可用性"
            if ! check_updates_available; then
                if [ "$force_update" = false ]; then
                    print_message $BLUE "已是最新版本，无需更新"
                    exit 0
                else
                    print_message $YELLOW "强制更新模式，继续执行"
                fi
            fi
            
            # 确认更新
            if [ "$force_update" = false ]; then
                read -p "确认执行更新? (y/N): " -n 1 -r
                echo
                if [[ ! $REPLY =~ ^[Yy]$ ]]; then
                    print_message $BLUE "更新已取消"
                    exit 0
                fi
            fi
            
            # 3. 创建备份
            if [ "$BACKUP_BEFORE_UPDATE" = true ]; then
                print_progress 3 8 "创建更新前备份"
                backup_path=$(create_update_backup) || ((update_errors++))
            fi
            
            # 4. 停止应用
            print_progress 4 8 "停止应用服务"
            stop_application || ((update_errors++))
            
            # 5. 更新代码和依赖
            print_progress 5 8 "更新应用代码"
            update_application_code || ((update_errors++))
            
            print_progress 6 8 "更新依赖包"
            update_dependencies || ((update_errors++))
            
            # 6. 运行数据库迁移
            print_progress 7 8 "运行数据库迁移"
            run_database_migrations || ((update_errors++))
            
            # 7. 启动应用
            print_progress 8 8 "启动应用服务"
            if ! start_application; then
                ((update_errors++))
                
                # 如果启动失败且启用了回滚
                if [ "$ROLLBACK_ON_FAILURE" = true ] && [ -n "$backup_path" ]; then
                    print_message $YELLOW "应用启动失败，尝试自动回滚..."
                    local backup_name=$(basename "$backup_path")
                    if rollback_to_backup "$backup_name"; then
                        print_message $YELLOW "自动回滚成功"
                        send_update_notification "UPDATE_FAILED_ROLLBACK_SUCCESS" "更新失败，已自动回滚到之前版本"
                        exit 1
                    else
                        print_message $RED "自动回滚也失败了！"
                        send_update_notification "UPDATE_FAILED_ROLLBACK_FAILED" "更新失败，自动回滚也失败，需要手动干预"
                        exit 1
                    fi
                fi
            fi
            
            # 8. 运行更新后测试
            if [ "$RUN_TESTS_AFTER_UPDATE" = true ]; then
                print_message $BLUE "运行更新后测试..."
                if ! run_post_update_tests; then
                    ((update_errors++))
                    
                    if [ "$ROLLBACK_ON_FAILURE" = true ] && [ -n "$backup_path" ]; then
                        print_message $YELLOW "测试失败，尝试自动回滚..."
                        local backup_name=$(basename "$backup_path")
                        if rollback_to_backup "$backup_name"; then
                            print_message $YELLOW "自动回滚成功"
                            send_update_notification "TEST_FAILED_ROLLBACK_SUCCESS" "更新后测试失败，已自动回滚"
                            exit 1
                        fi
                    fi
                fi
            fi
            
            # 清理旧备份
            cleanup_old_backups
            
            # 更新总结
            local update_end_time=$(date)
            print_title "更新总结"
            
            print_message $BLUE "开始时间: $update_start_time"
            print_message $BLUE "结束时间: $update_end_time"
            
            if [ $update_errors -eq 0 ]; then
                print_message $GREEN "✓ 应用更新成功完成"
                send_update_notification "UPDATE_SUCCESS" "应用更新成功完成"
            else
                print_message $YELLOW "⚠️  应用更新完成，但发现 $update_errors 个问题"
                send_update_notification "UPDATE_WARNING" "应用更新完成，但发现 $update_errors 个问题"
            fi
            
            print_message $BLUE "更新日志: $UPDATE_LOG"
            
            exit $update_errors
            ;;
    esac
}

# 错误处理
trap 'print_message $RED "更新过程中发生错误"; exit 1' ERR

# 运行主函数
main "$@"